package com.tengju.data.domain.shared.tree;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.tengju.data.domain.shared.IdObject;
import lombok.Getter;

import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static lombok.AccessLevel.PROTECTED;

@Getter(PROTECTED)
public class Tree<T extends TreeData> {

    private final TreeNode<T> root;

    @JsonIgnore
    protected Map<IdObject, TreeNode<T>> allTreeNodes = new ConcurrentHashMap<>();

    public Tree(TreeNode<T> root) {
        this.root = root;
    }

    public Tree(T rootData) {
        this.root = new TreeNode<>(rootData);
        allTreeNodes.put(root.getId(), root);
    }

    public TreeNode<T> getRoot() {
        return root;
    }

    public TreeNode<T> findNode(IdObject dataId) {
        return allTreeNodes.get(dataId);
    }

    public TreeNode<T> findNodeById(IdObject id) {
        return allTreeNodes.get(id);
    }

    void addNodes(TreeNode<T> parent, Set<TreeNode<T>> children) {
        if (!allTreeNodes.containsValue(parent)) {
            throw new TreeException(String.format("Unknown parent node %s", parent));
        }
        children.stream()
                .filter(Objects::nonNull)
                .filter(allTreeNodes::containsValue)
                .findFirst()
                .ifPresent(child -> {
                    throw new TreeException(String.format("Node with data %s already exist in this tree", child.getData()));
                });

        synchronized (root) {
            parent.addChildren(children);
            children.forEach(child -> allTreeNodes.put(child.getId(), child));
        }
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Tree<?> tree = (Tree<?>) o;
        return Objects.equals(root, tree.root);
    }

    @Override
    public int hashCode() {
        return Objects.hash(root);
    }


    public String print(TreeNode<T> node) {
        synchronized (root) {
            StringBuilder result = new StringBuilder();
            for (int i = 0; i < node.getDepth(); i++) {
                result.append("\t");
            }
            result.append("L").append(node.getDepth()).append(":\t").append(node.toString()).append("\n");
            if (node.getChildren() != null) {
                for (TreeNode<T> child : node.getChildren()) {
                    result.append(print(child));
                }
            }
            return result.toString();
        }
    }

    public Collection<TreeNode<T>> getAllNodes() {
        return allTreeNodes.values();
    }

    @Override
    public String toString() {
        return print(getRoot());
    }

}
